//
//  QRCodeTool.swift
//  二维码综合案例
//
//  Created by xmg
//  Copyright 小码哥. All rights reserved.
//

import UIKit
import AVFoundation

typealias ScanResult = ([String])->()

class QRCodeTool: NSObject {

    
    // 设置为单例
    static let shareInstance = QRCodeTool()
    
    // 暴漏一个属性, 让外界, 传值, 用来控制, 是否描绘扫描到的二维码边框
    var isDrawFlag: Bool = false
    
    // 会话的懒加载
    private lazy var session: AVCaptureSession = {
        // 3. 创建一个会话, 链接输入和输出
        let session = AVCaptureSession()
        return session
    }()
    
    // 输入的懒加载
    private lazy var input: AVCaptureDeviceInput? = {
        
        // 1. 获取摄像头设备, 并且作为输入设备
        let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
        var input: AVCaptureDeviceInput?
        do{
            input = try AVCaptureDeviceInput(device: device)
        }catch
        {
            print(error)
            return nil
        }
        return input
        
    }()
    
    // 输出的懒加载
    private lazy var output: AVCaptureMetadataOutput = {
        // 2. 设置输出处理
        // 元数据处理对象: 元数据, 就是一种中间数据
        let output = AVCaptureMetadataOutput()
        // 设置元数据处理代理, 接收处理结果
        output.setMetadataObjectsDelegate(self, queue:  dispatch_get_main_queue())
        return output
    }()
    
    // 预览图层的懒加载
    private lazy var preLayer: AVCaptureVideoPreviewLayer = {
        let layer = AVCaptureVideoPreviewLayer(session: self.session)
        return layer
    }()

    private var scanResultBlock: ScanResult?
    
    
    
    
}

// 扫描接口
extension QRCodeTool {
    
    func startScanQRCode(inView: UIView, resultBlock:ScanResult?)
    {
        
        // 1. 记录block, 在合适的地方执行
        scanResultBlock = resultBlock
        
        // 在添加之前, 先判断,当前会话是否可以添加
        if session.canAddInput(input) && session.canAddOutput(output)
        {
            session.addInput(input)
            session.addOutput(output)
            // 设置元数据处理的结果类型
            // 如果只需要处理二维码, 那么只需要把处理类型改为二维码类型就可以
            // output.availableMetadataObjectTypes
            // 这个设置, 一定要在会话添加输出处理之后, 才能设置, 否则, 扫描不到, 无法处理
            output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
        }
        
        // 3.1 添加一个视频预览图层
        preLayer.frame = inView.bounds
        guard let subLayers = inView.layer.sublayers else {
            inView.layer.insertSublayer(preLayer, atIndex: 0)
            return
        }
        
        if !subLayers.contains(preLayer)
        {
            inView.layer.insertSublayer(preLayer, atIndex: 0)
        }

        // 4. 开始扫描(启动会话)
        session.startRunning()
        
    }
    
    
    /**
     设置扫描区域
     
     - parameter sourceFrame: 需要扫描的最原始的区域
     */
    func setInterstRect(sourceFrame: CGRect)
    {
        // 3.2 设置扫描识别的区域
        // 坐标系: 0, 0 是右上角
        // 横屏状态下的坐标
        let bounds = UIScreen.mainScreen().bounds
        let x = sourceFrame.origin.x / bounds.size.width
        let y = sourceFrame.origin.y / bounds.size.height
        let w = sourceFrame.size.width / bounds.size.width
        let h = sourceFrame.size.height / bounds.size.height
        
        
        // 值域范围, 0---1
        output.rectOfInterest = CGRectMake(y, x, h, w)
    }
    
    
    
}


// 生成二维码接口, 识别图片二维码接口
extension QRCodeTool {
    /**
     根据外界传递过来的内容, 生成一个二维码图片, 并且, 可以根据参数, 添加小头像,在生成后的二维码中间
     
     - parameter content:        二维码内容
     - parameter bigImageSize:   大图片的尺寸
     - parameter smallIcon:      小图片
     - parameter smallImageSize: 小图片尺寸
     
     - returns: 合成后的图片
     */
    class func generatorQRCode(content: String, bigImageSize: CGFloat, smallIcon: UIImage?, smallImageSize: CGFloat) -> UIImage
    {
        // 0. 获取需要输入的数据
        let strData = content.dataUsingEncoding(NSUTF8StringEncoding)
        
        // 1. 创建一个二维码滤镜
        let filter = CIFilter(name: "CIQRCodeGenerator")
        // 恢复滤镜默认设置
        filter?.setDefaults()
        
        // 2. 设置滤镜的输入内容
        // 如果想要给滤镜设置输入数据, 只能通过KVC的形式, 设置 inputMessage
        // 输入的数据, 只能是NSData
        filter?.setValue(strData, forKey: "inputMessage")
        
        // 设置二维码的纠错率 inputCorrectionLevel
        // L, M, Q, H
        filter?.setValue("H", forKey: "inputCorrectionLevel")
        
        // 3. 直接从二维码滤镜中获取需要的二维码图片
        let imageCI = filter?.outputImage
        //        let image = UIImage(CIImage: imageCI!)
        //        print(image.size)
        
        // 3.1 我们需要借助位图来处理放大图片, 获取一个高清的图片
        var image = createBigImage(imageCI!, size: bigImageSize)
        
        if smallIcon != nil
        {
            image = createImage(image, smallImage: smallIcon!, sizeHW: smallImageSize)
            
        }
        
        // 设置图片
        return image
        
        
    }
    
    
    /**
     识别一个图片中所有的二维码, 获取二维码内容
     
     - parameter sourceImage:       需要识别的图片
     - parameter isDrawQRCodeFrame: 是否绘制识别到的边框
     
     - returns: 元组(识别出来的结果数组, 识别出来的绘制二维码图片)
     */
    class func detectorQRCodeImage(sourceImage: UIImage, isDrawQRCodeFrame: Bool) -> (resultStrs: [String], resultImage: UIImage)
    {
        // 1. 创建一个探测器
        let context = CIContext()
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy: CIDetectorAccuracyLow])
        
        // 2. 直接开始识别图片, 获取图片特征
        let imageCI =  CIImage(image: sourceImage)
        let features: [CIFeature] = detector.featuresInImage(imageCI!)
        
        
        // 3. 读取特征
        
        var tempImage = sourceImage
        // 遗留问题, 绘制二维码边框, 只能绘制一个?
        var resultArray = [String]()
        for feature in features
        {
            let tempFeature = feature as!  CIQRCodeFeature
            
            resultArray.append(tempFeature.messageString)
            
            
            if isDrawQRCodeFrame
            {
                // 绘制图片
                tempImage = drawQRCodeFrame(tempFeature, toImage: tempImage)
                
            }
        }
        
        // 返回描绘边框后的图片
        
        return (resultArray, tempImage)
        
    }
    
    
}

// MARK: - 私有方法
extension QRCodeTool {
    
    
    /**
     根据一个特征, 对给定图片, 进行绘制边框
     
     - parameter feature: 特征对象
     - parameter toImage: 需要绘制的图片
     
     - returns: 绘制好边框的图片
     */
    private class func drawQRCodeFrame(feature: CIQRCodeFeature, toImage: UIImage) -> UIImage
    {
        
        // bounds, 相对于原图片的一个大小
        // 坐标系, 是以左下角为0, 0
        let bounds = feature.bounds
        
        let size = toImage.size
        // 1. 开启图形上下文
        UIGraphicsBeginImageContext(size)
        
        // 2. 绘制图片
        toImage.drawInRect(CGRectMake(0, 0, size.width, size.height))
        // 3. 反转上下文坐标系
        let context = UIGraphicsGetCurrentContext()
        CGContextScaleCTM(context, 1, -1)
        CGContextTranslateCTM(context, 0, -size.height)
        
        // 3. 绘制边框
        let path: UIBezierPath = UIBezierPath(rect: bounds)
        path.lineWidth = 6
        UIColor.redColor().setStroke()
        path.stroke()
        
        
        // 4. 取出图片
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        // 5. 关闭上下文
        UIGraphicsEndImageContext()
        
        return image
        

    }

    
    /**
     根据CIImage生成指定大小的高清UIImage
     :param: image 指定CIImage
     :param: size    指定大小
     :returns: 生成好的图片
     */
    private class func createBigImage(image: CIImage, size: CGFloat) -> UIImage {
        
        let extent: CGRect = CGRectIntegral(image.extent)
        let scale: CGFloat = min(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent))
        
        // 1.创建bitmap;
        let width = CGRectGetWidth(extent) * scale
        let height = CGRectGetHeight(extent) * scale
        let cs: CGColorSpaceRef = CGColorSpaceCreateDeviceGray()!
        let bitmapRef = CGBitmapContextCreate(nil, Int(width), Int(height), 8, 0, cs, 0)!
        
        let context = CIContext(options: nil)
        let bitmapImage: CGImageRef = context.createCGImage(image, fromRect: extent)
        
        CGContextSetInterpolationQuality(bitmapRef,  CGInterpolationQuality.None)
        CGContextScaleCTM(bitmapRef, scale, scale);
        CGContextDrawImage(bitmapRef, extent, bitmapImage);
        
        // 2.保存bitmap到图片
        let scaledImage: CGImageRef = CGBitmapContextCreateImage(bitmapRef)!
        
        return UIImage(CGImage: scaledImage)
    }

    
    /**
     根据两个图片,合成一个大图片
     
     - parameter bigImage:   大的背景图片
     - parameter smallImage: 小的图标(中间)
     - parameter sizeHW:     小图标的尺寸
     
     - returns: 合成后的图片
     */
    private class func createImage(bigImage: UIImage, smallImage: UIImage, sizeHW: CGFloat) -> UIImage
    {
    
        let size = bigImage.size
        // 1. 开启一个图形上下文
        UIGraphicsBeginImageContext(size)
        
        // 2. 绘制大图片
        bigImage.drawInRect(CGRectMake(0, 0, size.width, size.height))
        
        // 3. 绘制小图片
        let x = (size.width - sizeHW) * 0.5
        let y = (size.height - sizeHW) * 0.5
        smallImage.drawInRect(CGRectMake(x, y, sizeHW, sizeHW))
        
        
        // 4. 取出合成图片
        let image = UIGraphicsGetImageFromCurrentImageContext()
        
        // 5. 关闭上下文
        UIGraphicsEndImageContext()
        
        return image
  
    }
    
    // 绘制二维码边框
    func drawQRCodeFrame(obj: AVMetadataMachineReadableCodeObject)
    {
        //        corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
        //         print(obj.corners)
        
        // 1. 必须使用视频预览图层, 对坐标进行转换
        let resultObj = preLayer.transformedMetadataObjectForMetadataObject(obj) as! AVMetadataMachineReadableCodeObject
        
        
        
        // 根据四个点, 绘制一个曲线,
        let layer: CAShapeLayer = CAShapeLayer()
        layer.strokeColor = UIColor.greenColor().CGColor
        layer.fillColor = UIColor.clearColor().CGColor
        layer.lineWidth = 3
        
        let path: UIBezierPath = UIBezierPath()
        
        // 对四个点的数据进行遍历, 并且根据里面的字典, 创建对应的point
        let pointCount = resultObj.corners.count
        for i in 0..<pointCount
        {
            let pointDic = resultObj.corners[i] as! CFDictionary
            
            var point: CGPoint = CGPointZero
            CGPointMakeWithDictionaryRepresentation(pointDic, &point)
            if i == 0
            {
                path.moveToPoint(point)
            }else
            {
                path.addLineToPoint(point)
            }
            
        }
        path.closePath()
        
        layer.path = path.CGPath
        
        // 添加形状图层, 到需要展示的图层上面
        preLayer.addSublayer(layer)
        
        
    }
    
    
    // 移除二维码边框
    func removeQRCodeFrame()
    {
        
        guard let subLayers = preLayer.sublayers else { return }
        for layer in subLayers
        {
            if layer.isKindOfClass(CAShapeLayer)
            {
                layer.removeFromSuperlayer()
            }
        }
        
        
    }

    
    
}


// MARK: - 识别到二维码之后调用的代理方法
extension QRCodeTool: AVCaptureMetadataOutputObjectsDelegate {
    // 识别到二维码之后调用
    func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
        
        
        removeQRCodeFrame()
        
        var resultStrs = [String]()
        // 以后, 如果扫描到其他码制, 也会调用这个方法, 所以, 在这里, 如果我们只是处理二维码, 需要对元数据数据类型, 进行判断处理
        for obj in metadataObjects
        {
            if obj.isKindOfClass(AVMetadataMachineReadableCodeObject)
            {
                // corners, 是二维码的四个角, 但是坐标如果想要使用, 需要进行转换
                // stringValue: 就是二维码对应的数据信息
                let resultObj = obj as! AVMetadataMachineReadableCodeObject
                
                resultStrs.append(resultObj.stringValue)
                
                if isDrawFlag
                {
                     drawQRCodeFrame(resultObj)
                }
                
               
                
            }
        }
        
        guard let resultBlock = scanResultBlock else {return}
        
        resultBlock(resultStrs)
        
    }
}








